import java.util.Stack;

/**
 * Classe GenerationFonction :  Génère les fonctions.
 * 
 * @author BlobbyCompiloTeam
 *
 */
public class GenerationFonction implements GramConstants
{
	
	private int taillePara;			// Taille des paramètres d'une fonction
	private int rangPara;			// Rang du paramètre courant
	private IdFonc idf;				// Identifiant IdFonc contenant les paramètres de la fonction
	private static MaP map;		// Objet MaP d'écriture du code

																// Création de 3 piles pour le contrôle de type
	private Stack<IdFonc> pileIdFonc;			// Contient les IdFonc des fonctions
	private Stack<Integer> pileIndexPara;		// Contient les indices des paramètres courants de chaque fonction
	private Stack<String> pileNomFonction;	// Contient les noms des fonctions
	
	private Stack<Boolean> pileEstDeclare;
	
	private boolean isInMain = false; // Vrai si on est dans le main du programme sinon faux. Evite les retourne dans le main
	
	/**
	 * @param isInMain the isInMain to set
	 */
	public void setInMain(boolean isInMain) {
		this.isInMain = isInMain;
	}


	/**
	 * Constructeur
	 * @param _map : Objet pour l'écriture du code en sortie
	 */
	public GenerationFonction(MaP _map){
		map = _map;
		taillePara = 0;
		rangPara = 0;
		
		pileIdFonc = new Stack<IdFonc>();
		pileIndexPara = new Stack<Integer>();
		pileNomFonction = new Stack<String>();

		pileEstDeclare = new Stack<Boolean>();
	}
	
	
	/**
	 * Déclare un nouvel Ident idFonc avec le type de retour de la fonction.
	 * @param type : type de retour de la fonction
	 */
	public void declIdFonc(int type){
		idf = new IdFonc(type);
	}
	
	
	/**
	 * Récupère le nom de la fonction. L'ajoute dans la pile pileNomFonction. Affichage.
	 * @param nomFonction : nom de la fonction
	 */
	public void pushNomFonction(String nomFonction) {
		pileNomFonction.push(nomFonction);
		map.fonc_nom(nomFonction);
	}
	
	
	/**
	 * Enregistrement d'un nouveau paramètre d'une déclaration de fonction. 
	 * Récupère sa taille, crée un nouvel IdPara (pour l'instant, on met dans l'offset de IdPara le rang du paramètre).
	 * Le range dans la table des locaux.
	 * @param table : table des identificateurs
	 * @param type : type du nouveau paramètre déclaré
	 */
	public void declPara(TabIdent table, int type)
	{
		switch(type){
			case typeEnt :
			case typeBool :
				taillePara += 2;
				break;
			default :
		}
		rangPara++;
		table.rangeIdentLocaux(GramTokenManager.identLu, new IdPara(type,rangPara));
	}
	
	
	/**
	 * Ajout du type du paramètre courant dans idf pour le contrôle de type.
	 * @param type : type du paramètre courant
	 */
	public void ajoutListPara(int type){
		idf.ajoutList(type);
	}
	
	
	/**
	 * Après avoir traité tous les paramètres de la fonction, on l'ajoute dans la table des globaux avec idf.
	 * @param table : table des identificateurs
	 */
	public void declFonction(TabIdent table){
		table.rangeIdentGlobaux(pileNomFonction.peek(), idf);
	}
	
	
	/**
	 * Tous les paramètres sont enregistrés dans la table des locaux. Calcul de l'offset de chaque paramètre.
	 * A ce moment, le champ offset de chaque paramètre contient son rang.
	 * @param table : table des identificateurs
	 */
	public void tailleCalcul(TabIdent table){
		table.calculOffsetLocaux(taillePara);
		taillePara = 0;
		rangPara = 0;
	}
	
	
	/**
	 * Fin de la déclaration de la fonction. On vide la table des locaux. Affichage. On dépile le nom de la fonction.
	 * @param table : table des identificateurs
	 */
	public void finFonc(TabIdent table){
		table.videLocaux();
		map.fin_bloc(pileNomFonction.peek());
		map.fonc_ferme_bloc(idf.nombrePara()*2);
		pileNomFonction.pop();
	}
	
	
	/**
	 * Contrôle si la valeur retournée est de même type que la valeur de retour déclarée dans l'en-tête de la fonction
	 * @param table : table des identificateurs
	 * @param type : type de la valeur retournée
	 */
	public void returnFonc(TabIdent table, int type){
		if(!isInMain){
			if(!ControleType.controleBinaire(type, table.chercheIdentGlobaux(pileNomFonction.peek()).getType(), opEg)) {
				MsgErreur.getErreur(Gram.token.beginLine, "GenerationFonction.returnFonc", "Erreur de type dans le retour de la fonction " + pileNomFonction.peek());
			}
		}
	}
	
	
	/**
	 * Affichage du retour de la fonction.
	 * Affichage de l'étiquette de fin de fonction.
	 */
	public void retourne(){
		if (!isInMain){
			map.fonc_retourneVal(idf.nombrePara()*2+4, pileNomFonction.peek());
		}else{
			MsgErreur.getErreur(Gram.token.beginLine, "GenerationFonction.retourne", "Interdiction de retour dans le main");
		}
	}
	
	
	/**
	 * Cherche dans la table le nom de la fonction passée en paramètre.
	 * Si la fonction n'est pas déclarée, on arrête en affichant un message d'erreur et on le signale dans pileEstDeclare.
	 * Ajoute le nom de la fonction dans la pile pileNomFonction.
	 * Ajoute l'idFonc associé dans la pile pileIdFonc.
	 * Ajoute l'index courant des paramètres dans la pile pileIndexPara.
	 * Affiche le val_retour.
	 * @param nomFonc : nom de la fonction
	 * @param table : table des identificateurs
	 * @return Vrai si la fonction existe dans la table des globaux, Faux sinon
	 */
	public boolean pushAppelIdFonc(String nomFonc, TabIdent table)
	{
		Ident id = table.chercheIdentGlobaux(nomFonc);
		
		if(id==null || !(id instanceof IdFonc))
		{	
			pileEstDeclare.push(false);
			pileNomFonction.push(nomFonc);
			MsgErreur.getErreur(Gram.token.beginLine, "GenerationFonction.pushAppelIdFonc", nomFonc + " fonction non déclarée.");
			return false;
		}
		
		pileNomFonction.push(nomFonc);
		pileIdFonc.push((IdFonc)id);
		pileIndexPara.push(new Integer(0));
		
		pileEstDeclare.push(true);
		
		map.fonc_val_retour();
		
		return true;
	}
	
	
	/**
	 * Si la fonction n'est pas déclaré, on ne fait rien.
	 * Contrôle de type entre le type du paramètre passé en argument de la fonction et le type déclaré dans l'en-tête de la fonction.
	 * Vérifie l'égalité et incrémente l'index pour passer au paramètre suivant.
	 * @param type : type du paramètre passé en argument
	 */
	public void verifParamCourant(int type){
		if(pileEstDeclare.peek()) {
			Integer index = pileIndexPara.pop();
			if(pileIdFonc.peek().nombrePara()>index){
				boolean b = ControleType.controleBinaire(pileIdFonc.peek().getTypePara(index), type, opEg);
				
				if(!b)
				{
					MsgErreur.getErreur(Gram.token.beginLine, "GenerationFonction.verifParamCourant", "Le type donné en paramètre en position " + index + " de la fonction " + pileNomFonction.peek() + " ne correspond pas.");
				}
			}else{
				MsgErreur.getErreur(Gram.token.beginLine, "GenerationFonction.verifParamCourant", "Le paramètre numéro " + index + " n'existe pas pour la fonction " + pileNomFonction.peek());
			}
			index++;
			pileIndexPara.push(index);
			
		}
	}
	
	/**
	 * Fonction vérifiant le nombre de paramètre de la fonction courante et le nombre passé dans le fichier
	 * Elle renvoit un message d'erreur si c'est deux nombres ne sont pas égaux
	 */
	public void verifNombrePara(){
		if (pileIndexPara.peek()<pileIdFonc.peek().nombrePara()){
			MsgErreur.getErreur(Gram.token.beginLine, "GenerationFonction.verifNombrePara", "Il n'y a pas assez de paramètres pour la fonction " + pileNomFonction.peek() + ".");
		}
		if (pileIndexPara.peek()>pileIdFonc.peek().nombrePara()){
			MsgErreur.getErreur(Gram.token.beginLine, "GenerationFonction.verifNombrePara", "Il y a trop de paramètres pour la fonction " + pileNomFonction.peek() + ".");
		}
	}
	
	/**
	 * Renvoie le type de la fonction dont le nom se trouve en haut de la pile pileNomFonction.
	 * @param table : table des identificateurs
	 * @return le type de retour de la fonction si la fonction est déclarée, le typeErreur sinon
	 */
	public int typeFonction(TabIdent table){
		if(pileEstDeclare.peek()) {
			return table.chercheIdentGlobaux((pileNomFonction.peek())).getType();
		} else {
			return typeErreur;
		}
	}
	
	
	/**
	 * Dépile la fonction courante des 3 piles. 
	 * Affiche l'appel à la fonction.
	 */
	public void popAppelIdFonc()
	{
		map.fonc_appel_proc(pileNomFonction.peek());
		
		if(pileEstDeclare.pop()) {
			pileIdFonc.pop();
			pileIndexPara.pop();
		}
		
		pileNomFonction.pop();
	}
	
	
	/**
	 * Affiche l'ouverture du bloc de la fonction pour les variables locales.
	 * @param offset : offset correspondant à la taile totale des variables locales
	 */
	public void ouv_bloc(int offset){
		map.fonc_ouv_bloc((offset + 2)*-1);
	}

}
